import logging
import doctest as dt
import subprocess


log = logging.getLogger(__name__)


class Example(object):
    """A ReST service request and expected response(s)
    """
    passed = None
    message = "This example could not be executed"

    def __init__(self, filename, description, method, url, data,
                 setup=None, teardown=None):
        self.filename = filename
        self.description = description
        self.method = method
        self.url = url
        self.data = data
        self.want = []
        self.setup = setup
        self.teardown = teardown

    def __call__(self, client):
        func = getattr(client, self.method.lower())
        self.response = response = func(self.url, self.data)
        match, message = self.compare(response)
        self.passed = match
        self.message = message
        if not match:
            raise AssertionError("Failed example:\n\n%s" % message)

    def compare(self, response):
        class Ex:
            pass

        # switch to html/xml or json mode based on
        # response content type
        checker = header_checker = dt.OutputChecker()
        try:
            headers = dict([(k.lower(), v) for k, v in response.headers])
            if ('xml' in headers['content-type']
                or 'html' in headers['content-type']):
                from trestle import xml
                checker = xml.LXMLOutputChecker()
            elif headers['content-type'] == 'application/json':
                from trestle import _json
                checker = _json.JSONOutputChecker()
        except (AttributeError, KeyError, ImportError), e:
            log.error("Error loading checker: %s %s", response.headers, e)
            pass

        for w in self.want:
            # we need a fake example to pass to the doctest output checker
            ex = Ex()
            try:
                ix = w.index(u'')
                headers, content = w[:ix+1], w[ix+1:]
            except ValueError:
                headers, content = [], w
            headers = '\n'.join(headers)
            content = '\n'.join(content) + '\n'

            check_flags = dt.NORMALIZE_WHITESPACE|dt.ELLIPSIS
            report_flags = dt.REPORT_UDIFF
            if headers:
                got_headers = '\n'.join(
                    [response.full_status] +
                    ["%s: %s" % h for h in response.headers]) + '\n'
                # FIXME better to check headers without respect to case or order
                match = header_checker.check_output(
                    headers, got_headers, check_flags)
                if not match:
                    ex.want = headers
                    out = (False, header_checker.output_difference(
                        ex, got_headers, report_flags))
                    if response.full_status.startswith('50'):
                        # server error; caller probably wants to know about that
                        out = (out[0],
                               out[1] + "Server error:\n" +
                               response.normal_body)
                    return out
            match = checker.check_output(
                content, response.normal_body, check_flags)
            if not match:
                ex.want = content
                return (False, checker.output_difference(
                    ex, response.normal_body, report_flags))
        return (True, None)

    def __str__(self):
        return "%s: %s %s (%s)" % \
               (self.filename, self.method.upper(), self.url, self.description)


class ShellExample(Example):
    """
    Example that executes a shell command and checks the stdout output of the
    command against the expected response. Use a ShellDirective to create a
    ShellExample.
    """

    def __init__(self, filename, command, expect,
                 setup=None, teardown=None, cwd=None, post=None, stderr=False):
        self.filename = filename
        self.command = command
        self.description = command
        self.expect = expect
        self.setup = setup
        self.teardown = teardown
        self.cwd = cwd
        self.post = post
        self.stderr = stderr

    def __call__(self, client):
        p = subprocess.Popen(
            self.command,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            close_fds=True,
            shell=True,
            cwd=self.cwd)

        returncode = p.wait()
        stdout, stderr = p.stdout.read(), p.stderr.read()
        if returncode != 0 or self.stderr:
            output = stderr
        else:
            output = stdout
        if self.post:
            output = self.post(output)
        match, message = self.compare(output)
        self.passed = match
        self.message = message

        if not self.passed:
            raise AssertionError("Failed example:\n\n%s" % self.message)

    def __str__(self):
        return "%s: %s" % (self.filename, self.command)

    def compare(self, output):
        class Ex:
            pass

        checker = dt.OutputChecker()
        check_flags = dt.NORMALIZE_WHITESPACE|dt.ELLIPSIS
        report_flags = dt.REPORT_UDIFF
        match = checker.check_output(self.expect, output, check_flags)
        if not match:
            ex = Ex()
            ex.want = self.expect + '\n'
            return (False,
                    checker.output_difference(ex, output, report_flags))
        return (True, None)
